/*->c.mnpllvl */
/*=============================================================================

                                 The Microcom MNP Library
                                        (Microsoft C Version)

-------------------------------------------------------------------------------

                         MNPLLVL - MNP Link Level Routines

-------------------------------------------------------------------------------

     Modification History

        3/25/87 - Compuserve V1.0

=============================================================================*/

/* Header files 
*/


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <ctype.h>
#include <time.h>

#include "h.mnpdat"
#include "h.llvl"
#include "h.mnp"


/* External references
*/
extern USIGN_16 sb_cnt;

/* Global buffers 
*/
struct link_ctl_blk lcb;

struct BUFFER rb, ftb, lkb, rlkb;

struct BLST lkblst[2],
            rlkblst[2],
            iatblst[8];

USIGN_8 lkbuff[MAX_LPDU_SZ * 2];
USIGN_8 rlkbuff[MAX_LPDU_SZ * 2];
USIGN_8 iatbuff[(64+5)*8];

USIGN_16 tbcnt;

struct TIMERS tmr;

USIGN_16 port_add,
        iir_add;

USIGN_8 linestat;




#ifdef NEVER

void mnptest(void)
{
 struct BLST *snd_struct1;                        /* LPDU structure */
 struct BLST *snd_struct2;
 int    i;

 link_init(&mnpcb);

 while(1)
 {
  printf("one\n");

  printf("num=%d used=%d\n",lkb.num,lkb.used);


  for(i=0;i<2;i++)
  {
   printf("next=%x bptr=%x\n",lkb.list[i].next_b,lkb.list[i].bptr);

  }


  printf("free=%x\n",lkb.free);
  printf("free next=%x\n",lkb.free->next_b);

  if(get_b(&lkb,&snd_struct1))
  {
   printf("get b fails 1");
   exit(1);
  }

  printf("num=%d used=%d\n",lkb.num,lkb.used);

  if(get_b(&lkb,&snd_struct2))
  {
   printf("get b fails 2");
   exit(1);
  }

  ret_b(&lkb,snd_struct1);
  ret_b(&lkb,snd_struct2);
 }

}


#endif



/*GLOBAL********************************************************************

        as_connect - establish an MNP link-connection

***************************************************************************/

SIGN_16 as_connect (struct MNP_CB * mnpcb,USIGN_16 mode)
{

SIGN_16 lr_cnt,
    la_cnt,
    retcode,
    link_state;

/* Initialize
*/
lcb.lpdu_type = 0;
link_state = IDLE;

for (;;)
        {
        switch (link_state)
                {

                case IDLE:
                        link_init(mnpcb);
                        link_reset();
                        if (L_ACCEPTOR)
                                {
                                SETBIT1(MODE)
                                link_state = LR_WAIT;
                                }
                        else
                                {
                                suspend(5);
                                if ((retcode = lpdu_send(LR,WAIT))!=NULL)
                                        link_state = LNK_ERROR;
                                else
                                        link_state = LR_RESP_WAIT;
                                }
                        break;

                case LR_RESP_WAIT:
                        lr_cnt = LR_RETRAN_CNT;
                        retcode = SUCCESS;
                        while ((retcode = receive_wait()) || !lcb.lpdu_type)
                                {
                                if (retcode == NO_PHYSICAL)
                                        {
                                        link_state = LNK_ERROR;
                                        break;
                                        }
                                if (lr_cnt--)
                                        {
                                       if((retcode = lpdu_send(LR,WAIT))!=NULL)
                                                {
                                                link_state = LNK_ERROR;
                                                break;
                                                }
                                        }
                                else
                                        {
                                        retcode = TIME_OUT;
                                        link_state = LNK_ERROR;
                                        break;
                                        }
                                }

                        if (retcode == SUCCESS) 
                                link_state = PARMS_NEGO;
                        break;

                case PARMS_NEGO:
                        if ((retcode = parse_lr())!=NULL)
                                {
                                as_disconnect(retcode,NULL);
                                return(retcode + LR_CODE);
                                }

                        if ((retcode = lpdu_send(LA,WAIT))!=NULL)
                                link_state = LNK_ERROR;
                        else
                                link_state = LNK_CONNECTED;
                        break;

                case LR_WAIT:
                        lr_cnt = 2;
                        while ((retcode = receive_wait()) || !lcb.lpdu_type)
                                {
                                if (retcode == NO_PHYSICAL)
                                        {
                                        link_state = LNK_ERROR;
                                        break;
                                        }
                                if (!(--lr_cnt))
                                        {
                                        retcode = TIME_OUT;
                                        link_state = LNK_ERROR;
                                        break;
                                        }
                                }

                        if (link_state != LR_WAIT)
                                break;

                        if ((retcode = parse_lr())!=NULL)
                                {
                                as_disconnect(retcode,NULL);
                                return(retcode + LR_CODE);
                                }

                        lr_cnt = LR_TRAN_CNT;
                        link_state = CONNECT_REQ_WAIT;
                        break;

                case CONNECT_REQ_WAIT:
                        if (lr_cnt == NULL)
                                {
                                retcode = FAILURE;
                                link_state = LNK_ERROR;
                                break;
                                }

                        if ((retcode = lpdu_send(LR,WAIT))!=NULL)
                                link_state = LNK_ERROR;
                        else
                                {
                                lr_cnt--;
                                la_cnt = LA_WAIT_CNT;
                                link_state = LA_WAIT;
                                }
                        break;

                case LA_WAIT:
                        while ((retcode = receive_wait()) || !lcb.lpdu_type)
                                {
                                if (retcode == NO_PHYSICAL)
                                        {
                                        link_state = LNK_ERROR;
                                        break;
                                        }
                                if (--la_cnt)
                                        {
                                       if((retcode = lpdu_send(LR,WAIT))!=NULL)
                                                {
                                                link_state = LNK_ERROR;
                                                break;
                                                }
                                        }
                                else
                                        {
                                        retcode = TIME_OUT;
                                        link_state = LNK_ERROR;
                                        break;
                                        }
                                }

                        if (link_state != LA_WAIT)
                                break;


                        switch (lcb.lpdu_type)
                                {
                                case LA:
                                        lcb.lt_rsn = NULL;

                                case LT:
                                        link_state = LNK_CONNECTED;
                                        break;

                                case LR:
                                        ret_b (&rlkb,rlkb.used_lst);
                                        link_state = CONNECT_REQ_WAIT;
                                        break;
        
                                default:
                                        retcode = FAILURE;
                                        link_state = LNK_ERROR; 
                                }

                        break;

                case LNK_ERROR:
                        as_disconnect(NULL,NULL);
                        return(retcode);

                case LNK_CONNECTED:
                        dphase_init();
                        SETBIT1(LINK_EST)
                        return(SUCCESS);
                }
        }
}

/*GLOBAL***********************************************************************

        as_disconnect - terminate a link-connection

******************************************************************************/

void as_disconnect(SIGN_16 lreason,SIGN_16 ureason)
{

/* If the link is still up, send an LD LPDU to the other side.  If the
** caller has supplied a reason code (in ureason) then save this code
** in the lcb so that it will be sent in the LD.  Wait for the LD to 
** actually be sent (to the very last byte...). 
*/
if (BIT1SET(LINK_EST))
        {
        if ((lcb.l_disc_code = lreason) == 255)
                lcb.u_disc_code = ureason;
        lpdu_send(LD,NOWAIT);
        while (!lne_stat() && modem_out_busy) pollzt();
        CLRBIT1(LINK_EST);
        }

/* In any case, wait a bit.
*/
suspend(10);

/* Now remove interrupt handlers - link driver and timers. 
*/
drvr_rem();
/* timerrem(); */

}

/*GLOBAL********************************************************************

        as_link - maintain a link-connection

***************************************************************************/

SIGN_16 as_link(void)
{

SIGN_16 retcode;

/* If the link-connection has failed, return reporting link down. 
*/
if (!BIT1SET(LINK_EST))
        return (LNK_DOWN);

/* If the physical connection has been lost, return reporting p-conn down. 
*/
if (lne_stat())
        return (NO_PHYSICAL);

/* If we support attention service, go see if we have any to process. 
*/
if (lcb.prot_level == 2)
        {
        if ((retcode = attn_process())!=NULL)
                return(retcode);
        }

/* Go see if it's time to send an ack. 
*/
if ((retcode = acking())!=NULL)
        return(retcode);

/* Try to free up any transmit buffers which have been acked. 
*/
tb_free();

/* Go see if we have to retransmit. 
*/
if ((retcode = retran_lt())!=NULL)
        return(retcode);

/* Make sure link is still up one more time 
*/
if (!BIT1SET(LINK_EST))
        return(LNK_DOWN);

/* Exit 
*/
return(SUCCESS);
}

/*LOCAL------------------------------------------------------------------------

        acking - send an LA LPDU if an ack condition is present

-----------------------------------------------------------------------------*/

SIGN_16 acking(void)
{
 SIGN_16 lt_unacked;                   /* number of unacked LTs */

/* If the force ack flag is set, send an LA right away.
*/
/* clr_int(); */                                     /* protect flag access */
if (BIT2SET(FORCE_ACK))                 /* reset flag if set */
        {
        CLRBIT2(FORCE_ACK)
        /* set_int(); */
        return(send_la());                      /* send LA and return */
        }
/* set_int(); */

/* If the window timer is enabled and has expired, send an LA. 
*/
if (BIT2SET(WNDW_TIMER) && (tmr.fcw == 0))
        return(send_la());

/* If there are unacknowledged LT's, it may be time to send an LA. 
*/
if (lcb.ltrsn_acked <= lcb.lt_rsn)
        lt_unacked = lcb.lt_rsn - lcb.ltrsn_acked;
else
        lt_unacked = (256 - lcb.ltrsn_acked) + lcb.lt_rsn;

if (lt_unacked)
        {

/* If there is no user data to send (i.e. no reason to put it off)
** send an LA.
*/
        if (sb_cnt == 0)
                return(send_la());

/* If the acknowledgment threshold has been reached, send an LA.
*/
        if (lt_unacked >= lcb.ack_threshold)
                return(send_la());

/* If the ack timer has elapsed, send an LA.  Otherwise, set the timer.
*/
        if (BIT1SET(ACK_TIMER))
                {
                if (tmr.ack)
                        return(SUCCESS);
                else
                        return(send_la());
                }
        else
                {
                SETBIT1(ACK_TIMER)
                tmr.ack = lcb.ack_timer;
                return(SUCCESS);
                }
        }

/* If there are no unacknowledged LT's, handle the 'zero window
** opening' case.
*/
else
        {
        if (BIT2SET(ZERO_WNDW) && credit_chk())
                {
                CLRBIT2(ZERO_WNDW)
                return(send_la());
                }
        }

/* Exit 
*/
return(SUCCESS);
}

/*LOCAL------------------------------------------------------------------------

        attn_process - handle break signalling

-----------------------------------------------------------------------------*/

int attn_process(void)
{

SIGN_16 retcode;

/* If the LN timer is set and has expired, it is time to retransmit the
** last LN LPDU sent.  However, if the retransmission limit has been
** reached, then the link is unusable.  Terminate the link and return
** with a function value=retran limit reached.
*/
if (BIT3SET(LN_TIMER) && (tmr.ln == 0))
        {
        if (lcb.ln_ret_cnt == RET_LIMIT)
                {
                as_disconnect(RETRAN_TMR_EXP,NULL);
                return(-67);
                }
        if ((retcode = lpdu_send(LN,NOWAIT))!=NULL)
                return(retcode);
        ++lcb.ln_ret_cnt;
        SETBIT3(LN_SENT)

     /* clr_int(); */
        SETBIT3(LN_TIMER)
        tmr.ln=lcb.lt_tmr;
     /* set_int(); */
        }

/* If an LN has been received, check its type for a destructive break
** signal.  Reset the link on receipt of a destructive break.
*/
/* clr_int(); */
if (BIT3SET(LN_RECEIVED))
        {
        CLRBIT3(LN_RECEIVED)                    
        if (lcb.ln_rtype == 1)
                {
                link_reset();
                dphase_init();
                }
      /*  set_int(); */
        if ((retcode = lpdu_send(LNA,NOWAIT))!=NULL)
                return(retcode);
        }
/* set_int(); */

/* Check for need to send an LNA
*/
if (BIT3SET(FORCE_LNA))
        {
        CLRBIT3(FORCE_LNA)
        if ((retcode = lpdu_send(LNA,NOWAIT))!=NULL)
                return(retcode);
        }

/* Exit 
*/
return(SUCCESS);
}

/*LOCAL------------------------------------------------------------------------

        credit_chk - determine local ability to receive

-----------------------------------------------------------------------------*/

SIGN_16 credit_chk(void)
{

extern USIGN_16 rb_cnt;

/* Compute the number of LT LPDUs which can be received.  This is
** the number of max sized LT's which will fit into the receive
** ring buffer.  This number can not be larger than maximum window
** (8 for this implementation).
*/
lcb.lcl_credit = (RBUF_LEN - rb_cnt) / lcb.max_data_sz;
if (lcb.lcl_credit > lcb.window_sz)
        lcb.lcl_credit = lcb.window_sz;

/* Exit.  Function value is receive credit. 
*/
return(lcb.lcl_credit);
}

/*LOCAL------------------------------------------------------------------------

        dphase_init - perform data phase initialization

-----------------------------------------------------------------------------*/

void dphase_init(void)
{

SIGN_16 i;

/* Set initial credits equal to negotiated maximum credit. 
*/
lcb.lcl_credit = lcb.rem_credit = lcb.window_sz;

/* Compute ack threshold 
*/
lcb.ack_threshold = (i = lcb.window_sz / 2) ? i : 1;

/* Be sure that ack conditions are reset. 
*/
CLRBIT1(LA_RECEIVED)
CLRBIT2(FORCE_ACK)

/* If window == 1, lt retran timer is specified to be 8 seconds
*/
if (lcb.window_sz == 1)
        lcb.lt_tmr = 8;
else
        {
        SETBIT2(WNDW_TIMER)
        tmr.fcw = lcb.window_tmr;
        }
}

/*LOCAL------------------------------------------------------------------------

        link_init - perform one-time link initialization

-----------------------------------------------------------------------------*/

void link_init(struct MNP_CB *mnpcb)
{

/* Initialize link control block values 
*/
lcb.status_1 = lcb.status_2 = lcb.status_3 = NULL;

ftb.num = lcb.window_sz = STRM_WNDW_SZ;
lcb.max_data_sz = STRM_DATA_SZ;

lcb.prot_level = 2;
lcb.srv_class = LCL_SCLASS;

lcb.ln_rsn = lcb.ln_ssn = lcb.ln_ret_cnt = tmr.ln = NULL;

switch (lcb.baud_rate = baudrate)
        {
        case B_1200:
                lcb.lt_tmr = LTTMR_12;
                lcb.window_tmr = W_TMR_12;
                break;

        case B_300:
                lcb.lt_tmr = LTTMR_3;
                lcb.window_tmr = W_TMR_3;
                break;

        case B_110:
                lcb.lt_tmr = LTTMR_110;
                lcb.window_tmr = W_TMR_110;
                break;

        case B_2400:
        default:
                lcb.lt_tmr = LTTMR_24;
                lcb.window_tmr = W_TMR_24;
                break;
        }

lcb.ack_timer = (lcb.lt_tmr/2)+1;

/* Initialize receive and transmit buffers 
*/
rlkb.list = rlkblst;
rlkb.num = 2;
lkb.list = lkblst;
lkb.num = 2;

init_blst(mnpcb->rlkb = &rlkb, MAX_LPDU_SZ, rlkbuff);
init_blst(mnpcb->lkb = &lkb, MAX_LPDU_SZ, lkbuff);

ftb.list = iatblst;
init_blst(mnpcb->ftb = &ftb,64+5, iatbuff);

/* Initialize async driver variables, too 
*/
p_mnpcb = mnpcb;
/* timerins(); */

drvr_ins(); 

}

/*LOCAL------------------------------------------------------------------------

        link_reset - 

-----------------------------------------------------------------------------*/

void link_reset(void)
{

/* Disable interrupts while resetting the link.
*/
/* clr_int(); */

/* Reset lcb values - note that attention sequence numbers are not
** reintialized.
*/
lcb.lt_rsn = lcb.lt_ssn = lcb.ltrsn_acked = lcb.ltssn_acked = NULL;
lcb.lt_ret_cnt = lcb.lpdu_type = NULL;

lcb.status_1 &= ~(DATA_READY | ACK_TIMER | RET_TIMER);
lcb.status_2 &= ~(FORCE_ACK | FORCE_RET | ZERO_WNDW | WNDW_TIMER);
lcb.status_3 &= ~(DUP_IGNORED);

/* Reset_timers 
*/
tmr.ack = tmr.fcw = tmr.lr = tmr.lt  = NULL;

/* Reset send and receive framers 
*/
sf_busy = sf_lt = modem_out_busy = FALSE;
rdle_flg = frame_snt = frame_rcvd = frame_dne = FALSE;
sf_state = SF_INIT;
rf_state = RF_INIT;

/* Reset transmit and receive buffers 
*/
reset_blst(&rb); 
reset_blst(&ftb);
reset_blst(&lkb);
reset_blst(&rlkb);

tbcnt = 0;

p_mnpcb->ld_reason = NULL;

/* Re-enable interrupts.
*/
/* set_int(); */

}

/*LOCAL------------------------------------------------------------------------

        lpdu_send - send an LPDU other than an LT

-----------------------------------------------------------------------------*/

SIGN_16 lpdu_send(SIGN_16 type,SIGN_16 wait)
{

SIGN_16 retcode;                                /* return code */
struct BLST *snd_struct;                        /* LPDU structure */

/* Get a buffer for the LPDU, send the LPDU, then release buffer. 
*/

if(get_b(&lkb,&snd_struct)) return(1);

retcode=send_pdu(type,wait,snd_struct);

ret_b(&lkb,snd_struct);

/* Exit 
*/
return(retcode);
}

/*LOCAL------------------------------------------------------------------------

        parse_lr - parse an LR LPDU and perform parameter negotiation

-----------------------------------------------------------------------------*/

SIGN_16 parse_lr(void)

{
 USIGN_8 *p;                    /* header pointer */
 USIGN_16 len, *pi;


/* Initialize 
*/
lcb.lr_parm = 0;
p = (rlkb.used_lst)->bptr;
len = *p++ - 2;

/* Process fixed fields 
*/
if (*p++ != LR)
        {
        ret_b(&rlkb,rlkb.used_lst);
        return(PROT_ERR);
        }

lcb.prot_level = min(lcb.prot_level,*p);
p++;

/* Process variable part 
*/
while (len > 0)
        {
        if (*p == 1)                    /* skip parm 1 - serial no. */
                {
                len -= 8;
                p += 8;
                continue;
                }

        if (*p == 2)                    /* take min of service class values */
                {
                len -= 3;
                p += 2;
                lcb.lr_parm |= LR_SRV_CLASS;
                lcb.srv_class = min(LCL_SCLASS, *p);
                p++;
                if (lcb.srv_class == 1)
                        SETBIT1(HDUPLEX)
                continue;
                }

        if (*p == 3)                    /* take min of window size */
                {
                len -= 3;
                p += 2;
                lcb.lr_parm |= LR_WNDW_SZ;
                lcb.window_sz = min (lcb.window_sz, *p);
                p++;
                continue;
                }

        if (*p == 4)
                {
                len -= 4;

                pi=(USIGN_16 *)(p+=2);

                lcb.lr_parm |= LR_DATA_SZ;
                lcb.max_data_sz = max (lcb.max_data_sz, *pi);

                p += 2;
                continue;
                }

        if (*p > 4)                     /* ignore anything else */
                {
                len -= *++p + 2;
                p += *p++;
                break;
                }
        }

/* All done with LR in buffer, return the buffer 
*/
ret_b(&rlkb,rlkb.used_lst);

/* Now check for parms not received and set default values 
*/
if (!(lcb.lr_parm & LR_SRV_CLASS))
        lcb.srv_class = 1;

if (!(lcb.lr_parm & LR_WNDW_SZ))
        lcb.window_sz = 1;

if (!(lcb.lr_parm & LR_DATA_SZ))
        lcb.max_data_sz = BLK_DATA_SZ;

/* There is such a thing as a block-mode MNP link-connection, but
** this implementation will not talk to one (block mode links are
** for use with higher-level MNP protocols). 
*/
if (lcb.max_data_sz == BLK_DATA_SZ)
        return(BAD_LR_PARMS);

/* Adjust the credit for any receive buffers already used.
*/
lcb.lcl_credit = lcb.window_sz - rb.used;

/* Exit 
*/

return(SUCCESS);
}

/*LOCAL------------------------------------------------------------------------

        receive_wait -  wait for an LPDU during link establishment

-----------------------------------------------------------------------------*/

SIGN_16 receive_wait(void)
{

/* Wait for an LPDU to be received or 2 seconds to go by, which ever
** occurs first.
*/
/* clr_int(); */
return(event_wait(2,FRAME_RCV));

}

/*LOCAL------------------------------------------------------------------------

        retran_lt - handle LT LPDU retransmission

-----------------------------------------------------------------------------*/

SIGN_16 retran_lt()
{

SIGN_16 i,
        retcode,
        not_done;
register USIGN_16 lts_unacked;
register struct BLST *snd_struct;

not_done = 1;
while (not_done)
        {

/* If there are no buffers in use, there is nothing to retransmit. 
*/
        if (ftb.used == 0)
                {
                CLRBIT2(FORCE_RET)                      /* reset force flag */
                return(SUCCESS);                        /* and just exit */
                }

/* Check for forced retransmission.  If so, clear flag and go right at
** it.  Otherwise, retransmit only when retran timer set and expired.
*/
        /* clr_int(); */
        if (BIT2SET(FORCE_RET))
                {
                CLRBIT2(FORCE_RET)
                /* set_int(); */
                }
        else
                {
                /* set_int(); */
                if (!BIT1SET(RET_TIMER))
                        return(SUCCESS);
        if (tmr.lt)
                return (SUCCESS);
        }

/* It's time to retransmit. But if we have retransmitted to the limit
** count, terminate the link.
*/
        if (lcb.lt_ret_cnt == RET_LIMIT)
                {
                as_disconnect(RETRAN_TMR_EXP,NULL);
                return(FAILURE);
                }

/* It's OK to still retransmit if there is remote credit (no need to
** check when window is only 1).
*/
        if (lcb.window_sz == 1)
                lcb.rem_credit = 0;
        else
                if (lcb.rem_credit == 0)
                        return(SUCCESS);

/* Retransmit ALL unacknowledged LT's.
*/
        snd_struct = ftb.used_lst;
        not_done--;

        if (lcb.lt_ssn >= lcb.ltssn_acked)
                lts_unacked = lcb.lt_ssn - lcb.ltssn_acked;
        else
                lts_unacked = (256 - lcb.ltssn_acked) + lcb.lt_ssn;

        while (lts_unacked)
                {
                lts_unacked--;
                if ((retcode = send_pdu(LT,NOWAIT,snd_struct))!=NULL)
                        return(retcode);

                if (lcb.window_sz == 1) 
                        event_wait(100,FRAME_SND);

                if ((retcode = acking())!=NULL)
                        return(retcode);
                if (lcb.prot_level == 2)
                        {
                        if ((retcode = attn_process())!=NULL)
                                return(retcode);
                        }

                snd_struct = snd_struct->next_b;

/* If forced retransmission, i.e., LA received, we may have received
** another LA acknowledging frames that we would retransmit next.  If
** this happened, free up acked transmit buffer(s), then go back to
** beginning.  note: if there are buffers in use, say 3 & 4, & if after
** 3 & 4 are retransmitted, we receive an LA for 3, then this logic will
** retransmit 4 again. The last 4 will be ignored by receiver as an 
** immediate duplicate.
*/
                if (BIT2SET(FORCE_RET) || BIT1SET(LA_RECEIVED))
                        {
                        i = ftb.used;
                        tb_free();
                        if (i != ftb.used)
                                {
/* Something has been freed.  If all buffers were freed, then we're
** all done.  If not, reset flags and do it over again.
*/
                                if (ftb.used)
                                        {
                                        SETBIT2(FORCE_RET)
                                        not_done++;
                                        break;
                                        }
                                }
                        }
                }

        if (!not_done)
                {
                lcb.lt_ret_cnt++;
                tmr.lt = lcb.lt_tmr;
                }
        }
        return(SUCCESS);
}

/*LOCAL------------------------------------------------------------------------

        send_la - send an LA LPDU

-----------------------------------------------------------------------------*/

SIGN_16 send_la()
{

SIGN_16 retcode;

/* Check for a zero window.  We want to remember sending a zero credit.
** We don't send zero credit, however, when the window is one. 
*/
if (!credit_chk())
        {
        SETBIT2(ZERO_WNDW)
        if (lcb.window_sz == 1)
                return(SUCCESS);
        }

/* Send an LA LPDU.  Return if link-connection is lost. 
*/
if ((retcode = lpdu_send(LA,NOWAIT))!=NULL)
        return(retcode);

/* Reset ack and flow control timers. 
*/
/* clr_int(); */
CLRBIT1(ACK_TIMER)
tmr.ack = NULL;
tmr.fcw = lcb.window_tmr;
/* set_int(); */

/* Exit 
*/
return(SUCCESS);
}

/*LOCAL------------------------------------------------------------------------

        send_pdu - send an LPDU

-----------------------------------------------------------------------------*/

SIGN_16 send_pdu(SIGN_16 type,SIGN_16 wait,struct BLST *snd_struct)
{

register USIGN_8 *p;
USIGN_8 *pli;
USIGN_16 *pi;
SIGN_16 i;


/* Just return if there is no physical connection. 
*/
if (lne_stat())
        return(NO_PHYSICAL);

/* If the lpdu is an LD, then we want to expedite it.  If an lpdu is being
** sent, wait a bit then truncate it.  This wait is necessary so that an
** empty frame (which is illegal) is not sent. 
*/
if (type == LD && sf_busy)
        {
        switch (lcb.baud_rate)
                {       
                case B_1200:
                        i = 10;
                        break;

                case B_300:
                        i = 20;
                        break;

                case B_110:
                        i = 30;
                        break;

                default:
                        i = 5;
                }

        suspend(i);
        sf_len = 0;
    
        while(sf_busy) pollzt();
        }

/* Wait for the send framer to finish if a frame is in progress.  
*/
/* clr_int(); */
while (sf_busy)
        if (send_wait())
                return(TIME_OUT);
/* set_int(); */                                     /* added 7-5-88 */

/* Now prepare the requested LPDU.
*/
p = snd_struct->bptr;

switch (type)
        {
        case LT:
                break;

        case LA:
                *p++ = LA_LEN;
                *p++ = LA;
                *p++ = 1;
                *p++ = 1;
                /* clr_int(); */
                *p++ = lcb.ltrsn_acked = lcb.lt_rsn;
                *p++ = 2;
                *p++ = 1;
                *p  = (USIGN_8) credit_chk();
                /* set_int(); */
                break;

        case LN:
                *p++ = LN_LEN;
                *p++ = LN;
                *p++ = 1;
                *p++ = 1;
                *p++ = lcb.ln_ssn;
                *p++ = 2;
                *p++ = 1;
                *p = lcb.ln_stype;
                break;

        case LNA:
                *p++ = LNA_LEN;
                *p++ = LNA;
                *p++ = 1;
                *p++ = 1;
                *p = lcb.ln_rsn;
                break;

        case LR:
                pli = p++;
                *p++ = LR;
                *p++ = lcb.prot_level;
                *p++ = 1;
                *p++ = 6;
                *p++ = 1;
                *p++ = 0;
                *p++ = 0;
                *p++ = 0;
                *p++ = 0;
                *p++ = 255;
                if (BIT1SET(MODE))
                        {
                        *pli = LR_ALEN;
                        if (lcb.lr_parm & LR_SRV_CLASS)
                                {
                                *p++ = 2;
                                *p++ = 1;
                                *p++ = lcb.srv_class;
                                *pli += 3;
                                
                                if (lcb.lr_parm & LR_WNDW_SZ)
                                        {
                                        *p++ = 3;
                                        *p++ = 1;
                                        *p++ = lcb.window_sz;
                                        *pli += 3;
                    
                                        if (lcb.lr_parm & LR_DATA_SZ)
                                                {
                                                *p++ = 4;
                                                *p++ = 2;
                                                pi = (USIGN_16 *) p;
                                                *pi = lcb.max_data_sz;
                                                *pli += 4;
                                                }
                                        }
                                }
                        }
                else
                        {
                        *pli = LR_ILEN;
                        *p++ = 2;
                        *p++ = 1;
                        *p++ = LCL_SCLASS;
                        *p++ = 3;
                        *p++ = 1;
                        *p++ = lcb.window_sz;
                        *p++ = 4;
                        *p++ = 2;
                        pi = (USIGN_16 *) p;
                        *pi = lcb.max_data_sz;
                        }
                break;

        case LD:
                *p++ = LD_LEN;
                *p++ = LD;
                *p++ = 1;
                *p++ = 1;
                if ((*p = lcb.l_disc_code) == 255)
                        {
                        p++;
                        *p++ = 2;
                        *p++ = 1;
                        *p = lcb.u_disc_code;
                        *(snd_struct->bptr) += 3;
                        }
                break;
        }

/* Pause a bit if this is a class 1 connection (hardly likely...)
*/
if (BIT1SET(HDUPLEX))
        suspend (20);

/* Set up the interrupt-driven send framer to send the lpdu.
*/
/* clr_int(); */
if (type == LT)
        sf_len = snd_struct->len;
else
        sf_len = *(snd_struct->bptr) + 1;

sf_ptr = snd_struct->bptr;
sf_busy = TRUE;
frame_snt = FALSE;                              /* added 7-15-88 */
trigger_sf();
/* set_int(); */

/* Wait for the lpdu to get out, if that was what the caller wanted.
*/
if(wait)
        if(send_wait()) 
                return(TIME_OUT);

/* Exit
*/
return(SUCCESS);
}

/*LOCAL------------------------------------------------------------------------

        send_wait - wait for transmission of an LPDU or timeout
        
-----------------------------------------------------------------------------*/

SIGN_16 send_wait()
{

/* Wait 55 seconds (arbitrary number which is larger than the time
** needed to send a long LPDU), or until the LPDU is sent. If the time
** expires and frame send has not completed, return time out.  This
** means, effectively, that the hardware is broken.
*/
/* clr_int(); */
return(event_wait(550,FRAME_SND));

}

/*LOCAL------------------------------------------------------------------------

        tb_free - process acknowledged LT LPDUs

-----------------------------------------------------------------------------*/

void tb_free(void)
{

register struct BLST *bp;
register USIGN_16 acked_lts;        /* to hold value between 0 and 65535 */
register USIGN_16 head_ssn;

/* If there are no LT LPDUs unacknowledged, just clear force retransmission
** flag and return. 
*/
if (!tbcnt)
        {
        CLRBIT2(FORCE_RET)
        return;
        }

/* Some LT LPDU's have been sent.  However, only try to free up transmit
** buffers if an LA has been received. 
*/
/* clr_int(); */
if (BIT1SET(LA_RECEIVED))       
        {
        CLRBIT1(LA_RECEIVED)
     /*  set_int(); */

/* Start freeing at the head of the list of buffers in use.  Free until
** the buffer just freed contains the LT with the same seq number as
** that of the last LT positively acked 
*/
        bp = ftb.used_lst;

        head_ssn = (USIGN_8)*(bp->bptr + LT_SEQ);

        if (head_ssn <= lcb.ltssn_acked)
                acked_lts = lcb.ltssn_acked - head_ssn +1;
        else
                acked_lts = (256 - head_ssn) + lcb.ltssn_acked + 1;

        while (acked_lts)
                {
                ret_b(&ftb,bp);
                bp = bp->next_b;
                acked_lts--;
                tbcnt--;
                }
        if (tbcnt < 0)
                tbcnt=0;

/* Clear force retransmission flag 
*/
        CLRBIT2(FORCE_RET)

/* If any LTs remain outstanding, reset the retransmission timer for them.
** Otherwise, be sure that the timer is cancelled. Since something has been
** acknowledged, the retransmission count can also be reset to 0. 
*/
        if (tbcnt)
                {
                SETBIT1(RET_TIMER)
                tmr.lt = lcb.lt_tmr;
                ftb.used_lst = bp;
                }
        else
                {
                CLRBIT1(RET_TIMER)
                tmr.lt = 0;
                }
        lcb.lt_ret_cnt = 0;
        }
else
        {
        /* set_int(); */
        }
}
